/*  -*- mode: c++; coding: utf-8; c-file-style: "stroustrup"; -*-

    Copyright 2008 Asier Aguirre <asier.aguirre@gmail.com>
    This file is part of Intromit.

    Intromit is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Intromit is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Intromit.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "source.hh"
#include "util.hh"
#include "file_mng.hh"
#include "string.hh"

#include <sys/types.h>
#include <regex.h>
#include <string.h>
#include <ctype.h>

// DEFINES
#define INC_FILE            32
#define INC_LINE           768
#define INC_COMM           768
#define INC_COM_STRING    4096
#define INC_FEAT_LINE      768

#define MAX_ATTR_STACK     128
#define MAX_FORMAT         128

// CLASS SOURCE_FILE

source_file::source_file()
{
    // file
    i_name=-1;
    data=NULL;
    n_data=0;
    n_line=-1;

    // lines
    util::ctor_vector(line, n_line, max_line);

    // comments
    util::ctor_vector(com, n_com, max_com);
    util::ctor_vector(com_str, n_com_str, max_com_str);

    // featured lines
    util::ctor_vector(feat, n_feat, max_feat);
}

source_file::~source_file()
{
    dtor();
}

void source_file::dtor()
{
    // file
    i_name=-1;
    if(data) data=(char*)realloc(data, 0);
    n_data=0;
    n_line=-1;

    // lines
    util::dtor_vector(line, n_line, max_line);

    // comments
    util::dtor_vector(com, n_com, max_com);
    util::dtor_vector(com_str, n_com_str, max_com_str);

    // featured lines
    util::dtor_vector(feat, n_feat, max_feat);
}

bool source_file::init(int i_name_, string_index *doc,
		       char *prefix, char *separator, char *ini, char *fin, char *neg, char *offline,
		       string_index *attr_index)
{
    dtor();
    i_name=i_name_;
    if(!load(doc->string(i_name))) return false;
    if(attr_index) postparse(prefix, separator, ini, fin, neg, offline, attr_index);
    return true;
}

bool source_file::load(char *filename)
{
    if(data) data=(char*)realloc(data, 0);
    n_data=0;

    if(!filename) ERROR_R0("no filename supplied");
    int max=0;
    if(!file_mng::load_n_max_nf<char>(data, n_data, max, filename))
    {
	n_data=0;
	return false; // "cannot load file: %s", filename
    }
    if(n_data+1>max) data=(char*)realloc(data, n_data+1);
    data[n_data]=0;
    preparse();
    return true;
}

void source_file::preparse_remove_comment_last_spaces()
{
    if(n_com_str && (!com_str[n_com_str-1] || com_str[n_com_str-1]=='\n'))
    {
	int tmp=n_com_str-1;
	while(n_com_str-2>=0 && isspace(com_str[n_com_str-2])) n_com_str--;
	com_str[n_com_str-1]=com_str[tmp];
    }
}

void source_file::preparse_add_comment_char(char c)
{
    int j=util::inc_vector(com_str, n_com_str, max_com_str, INC_COM_STRING);
    com_str[j]=c;
}

void source_file::preparse_add_comment(int &first_line, int &first_line_col, int col, char c)
{
    if(first_line==0 || first_line==3) // doxygen compat
    {
	if(c!='*' && c!='<' && c!='!' && c!='/' && c!='\n')
	    first_line++;
    }

    if(first_line==1) // first line previous space
    {
	if(!isblank(c) && c!='\n')
	{
	    first_line=2;
	    first_line_col=col;
	    preparse_add_comment_char(c);
	}
    }
    else if(first_line==2) // line content
    {
	if(c=='\n') first_line=3;
	preparse_add_comment_char(c);
    }
    else if(first_line==4 && (col>=first_line_col || !isblank(c))) // rest of lines previous space
    {
	first_line=2;
	preparse_add_comment_char(c);
    }
}

void source_file::preparse()
{
    // reset counters
    n_com=n_com_str=n_line=0;

    // status
    int string=0, line_comment=0, line_comment_continue=0, block_comment=0, col=0;
    int first_line=0, first_line_col=-1;

    for(int i=0; i<=n_data; i++)
    {
	// column
	if(data[i]=='\t') col+=8;
	else if(data[i]=='\n') col=0;
	else col++;

	// remove extrange space chars
	if(data[i]=='\r' || data[i]=='\f' || data[i]=='\v') data[i]=' ';

	// pointer to begining of lines
	if(!i || data[i-1]=='\n')
	{
	    int j=util::inc_vector(line, n_line, max_line, INC_LINE);
	    line[j]=i;
	    j=i-1;
	    if(i) data[j]=0;
	    while(j-1>=0 && isblank(data[j-1])) data[--j]=0;
	}
	if(i==n_data) break;

	// remove line terminator (also for \r)
	if(data[i]=='\r') data[i]=0;

	// manage strings and comments
	if(string)
	{
	    if(data[i]=='\"' && data[i-1]!='\\') string=0;
	}
	else if(line_comment)
	{
	    // inside block comment
	    preparse_add_comment(first_line, first_line_col, col, data[i]);

	    // line comment
	    if(data[i]=='\n')
	    {
		line_comment=0;
		line_comment_continue=1;
		com[n_com-1].end_line=n_line-1;
	    }
	    else data[i]=' ';
	}
	else if(block_comment)
	{
	    // block comment
	    if(data[i]=='*' && data[i+1]=='/')
	    {
		data[i]=data[i+1]=' ';
		block_comment=0;
		i++;
		col++;

		// terminator and end line
		preparse_add_comment_char(0);
		com[n_com-1].end_line=n_line-1;
	    }
	    else
	    {
		// inside block comment
		preparse_add_comment(first_line, first_line_col, col, data[i]);
		if(data[i]!='\n') data[i]=' ';
	    }

	    preparse_remove_comment_last_spaces();
	}
	else
	{
	    // check preprocessor
	    if(data[i]=='#' && (!i || data[i-1]!='\'') && !add_feature_line(n_line))
		MSG("internal: preprocessor feature line");

	    // code
	    if(data[i]=='\"' && (!i || data[i-1]!='\\')) string=1;
	    else if(data[i]=='/')
	    {
		if(data[i+1]=='/') {line_comment=1; i++; col++;}
		else if(data[i+1]=='*') {block_comment=1; i++; col++;}
		if((line_comment && !line_comment_continue) || block_comment)
		{
		    // add comment
		    int j=util::inc_vector(com, n_com, max_com, INC_COMM);
		    com[j].i_com=n_com_str;
		    com[j].i_attr=com[j].i_attr_block=-1;
		    com[j].ini_line=com[j].end_line=n_line-1;

		    // position inside comment
		    first_line=0;
		    first_line_col=col;
		}

		// remove /* or //
		if(line_comment || block_comment) data[i-1]=data[i]=' ';
	    }

	    // end of line comment group
	    if(line_comment_continue && ((data[i]!='\t' && data[i]!=' ') || block_comment))
	    {
		preparse_add_comment_char(0);
		preparse_remove_comment_last_spaces();
		line_comment_continue=0;
	    }
	}
    }
}

char *source_file::postparse_offline_attr(char *str, char *separator, char *neg)
{
    string_index si;
    int n_separator=strlen(separator);
    int n_neg=strlen(neg);

    while(isspace(*str) || *str==',') str++;
    while(*str)
    {
	char *spc=string::search(str, const_cast<char*>(" "), const_cast<char*>("()\"\""));
	char *com=string::search(str, const_cast<char*>(","), const_cast<char*>("()\"\""));
	char *stop;
	if(spc && (!com || spc<com)) stop=spc; else stop=com;
	if(stop) *stop=0;

	// negation cases
	if(!strncmp(str, neg, n_neg))
	{
	    // negated attribute
	    int i_str;
	    if((i_str=si.search_string_index(str+n_neg))>=0)
		// possitive attribute, delete
		si.delete_string(i_str);
	}
	else
	{
	    // positive attribute
	    int i_str;
	    if((i_str=si.search_string_index_format("%s%s", neg, str))>=0)
		// possitive attribute, delete
		si.delete_string(i_str);
	}

	// add attribute
	si.search_add_string(str);

	if(stop) str=stop+1; else break;
	while(isspace(*str) || *str==',') str++;
    }

    // create normalized string
    if(!si.n_string()) return NULL;
    char *dev=(char*)malloc(si.get_n_doc()+n_separator*si.n_string());
    str=dev;
    for(int i=0; i<si.n_string(); i++)
    {
	if(i) {memcpy(str, separator, n_separator); str+=n_separator;}

	char *attr;
	int n_attr=strlen(attr=si.i_string(i));
	memcpy(str, attr, n_attr); str+=n_attr;
    }
    *str=0;
    return dev;
}

void source_file::postparse_offline_block(char *off, char *separator, char *neg, string_index *attr_index, int i_namespace)
{
    int i_local_namespace=-1;
    char *namespc=attr_index->string(i_namespace);
    int n_namespc=namespc?strlen(namespc):0;

    while(*off)
    {
	char *colon=string::search(off, const_cast<char*>(":"), const_cast<char*>("()\"\""));
	char *brack=string::search(off, const_cast<char*>("{"), const_cast<char*>("()\"\""));

	// which is first : or {
	char *stop;
	if(colon && (!brack || colon<brack)) stop=colon; else stop=brack;
	if(!stop) break;
	*stop=0;

	// extract id
	char *id=off;

	while(1)
	{
	    // separate different id's
	    while(isspace(*id)) id++;
	    char *comma=string::search(id, const_cast<char*>(","), const_cast<char*>("<>()\"\""));
	    if(!comma) comma=stop;
	    *comma=0;
	    char *tmp=comma;
	    while(tmp>id && isspace(*(tmp-1))) {tmp--; *tmp=0;}

	    if(*id)
	    {
		if(i_namespace<0)
		    i_local_namespace=attr_index->search_add_string(id);
		else
		{
		    // compose namespace
		    int size=strlen(id) + 2 + n_namespc + 1;
		    char *local_namespace=(char*)malloc(size);
		    snprintf(local_namespace, size, "%s::%s", namespc, id);
		    i_local_namespace=attr_index->search_add_string(local_namespace);
		    free(local_namespace);
		}

		// debug: namespace id
		//ERROR("ID: {%s}", attr_index->string(i_local_namespace));
	    }

	    char *stop2, cstop2=0;

	    // colon
	    if(colon==stop)
	    {
		// extract attribs
		off=stop+1;
		stop2=string::search(off, const_cast<char*>("\n"), const_cast<char*>("()\"\""));
		if(!stop2 || (brack && brack<stop2)) stop2=brack;
		if(stop2)
		{
		    cstop2=*stop2;
		    *stop2=0;
		}

		// check : in attribs
		char *colon2;
		if((colon2=string::search(off, const_cast<char*>(":"), const_cast<char*>("()\"\""))))
		{
		    if(stop2) *stop2=cstop2;
		    stop2=colon2;
		    cstop2=*stop2;
		    *stop2=0;
		}

		off=strdup(off);

		char *attrs;
		if(i_local_namespace>=0 && (attrs=postparse_offline_attr(off, separator, neg)))
		{
		    // compose attr entry
		    char *local_namespace=attr_index->string(i_local_namespace);
		    int size=1 + strlen(local_namespace) + 1 + strlen(attrs) + 1;
		    char *entry=(char*)malloc(size);

		    // search if that namespace is already used, if so, update
		    int n_local_namespace=snprintf(entry, size, "@%s@", local_namespace);
		    int i_attr;
		    if((i_attr=attr_index->search_prefix_string_index(entry))>=0)
		    {
			// join previous_attr with attrs
			char *previous_attr=attr_index->i_string(i_attr)+n_local_namespace;
			char *joint=source_file::postparse_join_attributes(previous_attr, attrs, separator, neg);
			attr_index->delete_string(i_attr);
			free(attrs);
			attrs=joint;
			size+=strlen(attrs)+1;
			entry=(char*)realloc(entry, size);
		    }

		    snprintf(entry+n_local_namespace, size-n_local_namespace, "%s", attrs);
		    attr_index->search_add_string(entry);

		    // debug: generated entries (metadata attribute locators)
		    //ERROR("ENTRY: {%s}", entry);

		    free(entry);
		    free(attrs);
		}

		free(off);
		if(stop2) *stop2=cstop2; else stop2=stop-1; // to break main loop
	    }
	    // bracket
	    else
	    {
		// limit content to that of block
		off=stop+1;
		stop2=string::search(off, const_cast<char*>("}"), const_cast<char*>("()\"\"{}"));
		if(!stop2) MSG_R("mismatched brackets in offline metadata attributes at:\n%s", off);
		*stop2=0;
		off=strdup(off);
		postparse_offline_block(off, separator, neg, attr_index,
					i_local_namespace<0?i_namespace:i_local_namespace);
		free(off);
		*stop2='}';

		// reset namespace after a block
		i_local_namespace=i_namespace;
	    }

	    // next id loop?
	    if(comma==stop)
	    {
		off=stop2+1;
		break;
	    }
	    else id=comma+1;
	}
    }
}

void source_file::postparse_offline(char *off, char *separator, char *neg, string_index *attr_index)
{
    // first pass, remove comments and tabs
    char *tmp=off;
    while(*tmp)
    {
	if(*tmp=='"')
	{
	    // do not remove # in strings
	    tmp++;
	    while(*tmp && *tmp!='"') tmp++;
	    if(*tmp) tmp++;
	}
	else if(*tmp=='#')
	    while(*tmp && *tmp!='\n') {*tmp=' '; tmp++;}
	else
	{
	    if(*tmp=='\t' || *tmp=='\r') *tmp=' ';
	    tmp++;
	}
    }

    // substitude ",spaces\n" by ",spaces" and the same with ":"
    tmp=off;
    char *newline;
    while((newline=string::search(tmp, const_cast<char*>("\n"), const_cast<char*>("()\"\""))))
    {
	char *tmp2=newline-1;
	while(tmp2>tmp && (isblank(*tmp2) || *tmp2=='\n')) tmp2--;
	if(*tmp2==',' || *tmp2==':')
	{
	    tmp2++;
	    while(tmp2<=newline) {*tmp2=' '; tmp2++;}
	}
	else tmp=newline+1;
    }

    postparse_offline_block(off, separator, neg, attr_index, -1);
}

char *source_file::postparse_join_attributes(char *str1, char *str2, char *separator, char *neg)
{
    int n_separator=strlen(separator);
    int n_neg=strlen(neg);
    string_index si;
    for(int i=0; i<2; i++)
    {
	char *str=i?str2:str1;
	if(!str) continue;
	while(*str)
	{
	    char *tmp=string::search(str, separator, const_cast<char*>("()\"\""));
	    char *sep=tmp;
	    if(tmp==str) {str+=n_separator; continue;}
	    if(!tmp) tmp=str+strlen(str);
	    char ctmp=*tmp;
	    *tmp=0;

	    // negation cases
	    if(!strncmp(str, neg, n_neg))
	    {
		// negated attribute
		int i_str;
		if((i_str=si.search_string_index(str+n_neg))>=0)
		    // possitive attribute, delete
		    si.delete_string(i_str);
	    }
	    else
	    {
		// positive attribute
		int i_str;
		if((i_str=si.search_string_index_format("%s%s", neg, str))>=0)
		    // possitive attribute, delete
		    si.delete_string(i_str);
	    }

	    si.search_add_string(str);
	    *tmp=ctmp;
	    str=tmp;
	    if(sep) str+=n_separator;
	}
    }

    // create normalized string
    if(!si.n_string()) return NULL;
    char *dev=(char*)malloc(si.get_n_doc()+n_separator*si.n_string()), *str=dev;
    for(int i=0; i<si.n_string(); i++)
    {
	if(i) {memcpy(str, separator, n_separator); str+=n_separator;}

	char *attr;
	int n_attr=strlen(attr=si.i_string(i));
	memcpy(str, attr, n_attr); str+=n_attr;
    }
    *str=0;
    return dev;
}

char *source_file::postparse_normalize_attributes(char *&str, char *prefix, char *separator,
						  char *ini, char *fin, char *neg,
						  char *offline, string_index *attr_index,
						  bool *have_ini, bool*have_fin)
{
    int n_prefix=strlen(prefix);
    int n_separator=strlen(separator);
    int n_ini=strlen(ini);
    int n_fin=strlen(fin);
    int n_neg=strlen(neg);
    int n_offline=offline?strlen(offline):0;
    string_index si;
    *have_ini=0;
    *have_fin=0;

    // check prefix
    if(memcmp(str, prefix, n_prefix)) ERROR_R0("procedural: attributes expression must begin by prefix");
    char *dev=str;
    str+=n_prefix;

    // extract attr
    while(1)
    {
	// separator
	if(!memcmp(str, separator, n_separator)) {str+=n_separator; continue;}
	if(!memcmp(str, prefix, n_prefix)) {str+=n_prefix; continue;}
	if(!memcmp(str, ini, n_ini)) {str+=n_ini; *have_ini=true; continue; }
	if(!memcmp(str, fin, n_fin)) {str+=n_fin; *have_fin=true; continue; }

	// extract name
	char *name=str;
	if(!memcmp(str, neg, n_neg)) str+=n_neg;
	while(isalnum(*str) || *str=='_') str++;
	if(*str=='(')
	{
	    if(!(str=string::search(str+1, const_cast<char*>(")"), const_cast<char*>("()\"\"")))) ERROR_R0("mismatched parenthesis");
	    str++;
	}
	if(name==str) break;

	char ctmp=*str;
	*str=0;

	// negation cases
	if(!strncmp(name, neg, n_neg))
	{
	    // negated attribute
	    int i_str;
	    if((i_str=si.search_string_index(name+n_neg))>=0)
		// possitive attribute, delete
		si.delete_string(i_str);
	}
	else
	{
	    // positive attribute
	    int i_str;
	    if((i_str=si.search_string_index_format("%s%s", neg, name))>=0)
		// possitive attribute, delete
		si.delete_string(i_str);
	}

	// offline metadata attributes
	if(offline && !strncmp(offline, name, n_offline))
	{
	    if(name[n_offline]=='(')
	    {
		str[-1]=0;
		postparse_offline(&name[n_offline+1], separator, neg, attr_index);
		str[-1]=')';
	    }
	}
	else
	    // add this attribute
	    si.search_add_string(name);
	*str=ctmp;
    }

    // create normalized string
    char *end=str;
    str=dev;
    for(int i=0; i<si.n_string(); i++)
    {
	if(i) {memcpy(str, separator, n_separator); str+=n_separator;}

	char *attr;
	int n_attr=strlen(attr=si.i_string(i));
	memcpy(str, attr, n_attr); str+=n_attr;
    }
    *str=0;
    str=end;
    return dev;
}

void source_file::postparse(char *prefix, char *separator, char *ini, char *fin, char *neg, char *offline, string_index *attr_index)
{
    int n_prefix=strlen(prefix);
    int stack[MAX_ATTR_STACK], n_stack=0;
    for(int i=0; i<n_com; i++)
    {
	// attributes only for this comment
	char *local_attrs=NULL;

	char *str=&com_str[com[i].i_com], *wrt=str, *wrt_ini=str;
	while(*str)
	{
	    if(!memcmp(prefix, str, n_prefix))
	    {
		bool have_ini, have_fin;
		char *pos_attr=str;
		char *norm_attrs;
		if(!(norm_attrs=postparse_normalize_attributes(str, prefix, separator, ini, fin, neg,
							       offline, attr_index,
							       &have_ini, &have_fin))) break;

		// remove duplicated, leading and trailing spaces
		if(isblank(pos_attr[-1]))
		{
		    if(pos_attr[-1]==str[0]) str++;
		    if(wrt>wrt_ini && (!str[0] || str[0]=='\n')) wrt--;
		}

		if(have_ini && !have_fin)
		{
		    // add attributes to currently stacked ones
		    if(n_stack)
		    {
			// add to last level of stack
			char *last_attrs=attr_index->string(stack[n_stack-1]);
			char *join_attrs=postparse_join_attributes(last_attrs, norm_attrs, separator, neg);
			stack[n_stack++]=attr_index->search_add_string(join_attrs);
			free(join_attrs);
		    }
		    else
		    {
			// first level stack
			stack[n_stack++]=attr_index->search_add_string(norm_attrs);
		    }
		}
		else if(have_fin && !have_ini)
		{
		    // remove a stack level
		    if(n_stack) n_stack--;
		    else MSG("procedural: mismatched attribute block");
		}
		else if(!have_ini && !have_fin)
		{
		    // local attributes
		    if(!local_attrs) local_attrs=strdup(norm_attrs);
		    else
		    {
			char *join_attrs=postparse_join_attributes(local_attrs, norm_attrs, separator, neg);
			free(local_attrs);
			local_attrs=join_attrs;
		    }
		}
		continue;
	    }
	    if(wrt!=wrt_ini || *str!='\n')
	    {
		*wrt=*str;
		wrt++;
	    }
	    str++;
	}
	*wrt=0;

	// store local and block attributes
	if(n_stack) com[i].i_attr_block=stack[n_stack-1];
	if(local_attrs)
	{
	    com[i].i_attr=attr_index->search_add_string(local_attrs);
	    free(local_attrs);
	}
    }
}

int source_file::search_feature_line(int line)
{
    int ini=0, end=n_feat;
    while(end>ini)
    {
	int mid=(ini+end)>>1;
	if(feat[mid]>=line) end=mid; else ini=mid+1;
    }
    return ini;
}

int source_file::search_line(int i_data)
{
    int ini=0, end=n_line;
    while(end>ini)
    {
	int mid=(ini+end)>>1;
	if(line[mid]>=i_data) end=mid; else ini=mid+1;
    }
    return ini;
}

int source_file::search_comment(int end_line)
{
    int ini=0, end=n_com;
    while(end>ini)
    {
	int mid=(ini+end)>>1;
	if(com[mid].end_line>=end_line) end=mid; else ini=mid+1;
    }
    return ini;
}

bool source_file::add_feature_line(int i_line)
{
    i_line--; // line starts with 0
    int i=search_feature_line(i_line);
    if(i<n_feat && feat[i]==i_line) return true; // repeated

    util::inc_vector(feat, n_feat, max_feat, INC_FEAT_LINE);
    if(n_feat-i) memmove(&feat[i+1], &feat[i], (n_feat-i)*sizeof(int));
    feat[i]=i_line;
    return true;
}

char *source_file::comment(int i_line)
{
    // check file
    if(!n_data) return NULL;

    // check line
    i_line--; // line starts with 0
    if(i_line<0 || i_line>=n_line) ERROR_R0("line %d/%d out of bounds", i_line, n_line);

    // check feature line
    int i_feat=search_feature_line(i_line);
    if(i_feat>=n_feat || feat[i_feat]!=i_line) ERROR_R0("feature line not registered %d", i_line);

    // previous comment
    int i_com=search_comment(i_line); // com[i_com-1].end_line < i_line (from 0)
    if(i_com &&                       // comment at least one line before feature line
       (!i_feat || feat[i_feat-1]<com[i_com-1].ini_line))
    {
	// check spaces between [end_line+1 and i_line)
	int i;
	for(i=com[i_com-1].end_line+1; i<i_line; i++)
	{
	    int j;
	    for(j=line[i]; data[j]; j++) if(!isblank(data[j])) break;
	    if(!data[j]) break; // blank line
	}
	if(i==i_line)
	{
	    // previous comment is valid
	    return &com_str[com[i_com-1].i_com];
	}
    }

    // inline comment
    if(com[i_com].ini_line==i_line) return &com_str[com[i_com].i_com];

    // no comment
    return NULL;
}

char *source_file::attributes(int i_line, string_index *attr_index, char *separator, char *neg)
{
    // check file
    if(!data) return NULL;

    // check line
    i_line--; // line starts with 0
    if(i_line<0 || i_line>=n_line) ERROR_R0("line %d/%d out of bounds", i_line, n_line);

    // check feature line
    int i_feat=search_feature_line(i_line);
    if(i_feat>=n_feat || feat[i_feat]!=i_line) ERROR_R0("feature line not registered %d", i_line);

    // reset output
    int i_attr=-1, i_attr_block=-1;

    // previous attr
    int i_com=search_comment(i_line); // com[i_com-1].end_line < i_line (from 0)
    if(i_com &&                       // comment at least one line before feature line
       (!i_feat || feat[i_feat-1]<com[i_com-1].ini_line))
    {
	// check spaces between [end_line+1 and i_line)
	int i;
	for(i=com[i_com-1].end_line+1; i<i_line; i++)
	{
	    int j;
	    for(j=line[i]; data[j]; j++) if(!isblank(data[j])) break;
	    if(!data[j]) break; // blank line
	}
	if(i==i_line)
	{
	    // previous comment is valid
	    i_attr=com[i_com-1].i_attr;
	}
    }

    // inline attr
    if(i_attr==-1 && i_com<n_com && com[i_com].ini_line==i_line) i_attr=com[i_com].i_attr;

    // block attr
    if(i_com) i_attr_block=com[i_com-1].i_attr_block;

    // join attributes
    char *joint=postparse_join_attributes(attr_index->string(i_attr_block), attr_index->string(i_attr),
					  separator, neg);
    int i_joint=attr_index->search_add_string(joint);
    free(joint);
    return attr_index->string(i_joint);
}

int source_file::match_regex(char *regex, int line_min, int line_max)
{
    // check file
    if(!n_data) return -1;

    line_min--;
    line_max--;
    int i_com=search_comment(line_min);
    while(i_com<n_com && com[i_com].ini_line<line_min) i_com++;

    // regex
    regex_t rgx;
    if(regcomp(&rgx, regex, REG_EXTENDED)) ERROR_R_1("cannot compile regex: \"%s\"", regex);

    // loop
    int dev=-1;
    while(i_com<n_com && com[i_com].ini_line<=line_max)
    {
	if(!regexec(&rgx, &com_str[com[i_com].i_com], 0, NULL, 0))
	{
	    // match
	    dev=com[i_com].ini_line;
	    break;
	}
	i_com++;
    }

    // regex free and exit
    regfree(&rgx);
    return dev;
}

string_index *source_file::locate_class_method(int i_name_class, int line_class, int i_method_name, string_index *doc)
{
    // check file
    if(!n_data) return NULL;

    // start position (only have the line of the '{' )
    int lcm_pos=line[line_class-1];
    int lcm_block=0;
    int lcm_init=-1;

    // precalc
    int n_method_name=strlen(doc->string(i_method_name));

    // first asumption, the first '{' in the line is the class block opener
    while(data[lcm_pos]!='{')
    {
	if(!data[lcm_pos]) ERROR_R0("error no '{' class/struct opener");
	lcm_pos++;
    }
    lcm_pos++;

    // traverse class body
    string_index *dev=NULL;
    int all_ok=0;
    while(lcm_pos<n_data)
    {
	char c=data[lcm_pos];

	// whitespace and end of lines
	if((unsigned char)c<=' ')
	{
	    lcm_pos++;
	    continue;
	}

	// strings
	if(c=='\"' && data[lcm_pos-1]!='\\')
	{
	    lcm_pos++;
	    while(data[lcm_pos]!='\"' || data[lcm_pos-1]=='\\') lcm_pos++;
	    lcm_pos++;
	    continue;
	}

	// real analysis
	if(!lcm_block)
	{
	    if(c==':')
	    {
		lcm_init=-1;
		lcm_pos++;
		continue;
	    }
	    if(c=='{' || c==';')
	    {
		if(lcm_init>=0)
		{
		    // possible declaration
		    locate_class_method(doc->string(i_method_name), n_method_name, dev, lcm_init, lcm_pos);
		    lcm_init=-1;
		}
	    }
	    else if(lcm_init<0) lcm_init=lcm_pos;
	}

	// block init
	if(c=='{')
	{
	    lcm_block++;
	    lcm_pos++;
	    continue;
	}

	// block end
	if(c=='}')
	{
	    if(!lcm_block)
	    {
		// class end
		all_ok++;
		break;
	    }
	    lcm_block--;
	    lcm_pos++;
	    continue;
	}

	lcm_pos++;
    }

    if(all_ok) return dev;
    if(dev) delete dev;
    return NULL;
}

void source_file::locate_class_method(char *method_name, int n_method_name, string_index *&doc, int ini, int end)
{
    // check if method_name is operator
    int is_operator=0, size_operator=-1;
    if(strstr(method_name, "operator"))
    {
	is_operator++;
	size_operator=n_method_name-8/*operator*/;
	n_method_name=8/*operator*/;
    }

    // search method name
    int i;
    if(end-n_method_name<ini) return;
    for(i=ini; i<end-n_method_name; i++) if(!strncmp(&data[i], method_name, n_method_name)) break;
    if(i==end-n_method_name) return;

    // method name matches in i, check that method_name is not a substring
    int j=i+n_method_name;
    if(isalnum(data[j]) || data[j]=='_' ||
       isalnum(data[i-1]) || data[i-1]=='_' || data[i-1]=='~') return;

    // check real operator
    if(is_operator)
    {
	while((unsigned char)data[j]<=' ') j++;
	if(memcmp(&data[j], method_name+8/*operator*/, size_operator)) return;
	j+=size_operator;
    }

    // next non-whitespace character must be '('
    while((unsigned char)data[j]<=' ') j++;
    if(data[j]!='(') return;
    j++;
    while((unsigned char)data[j]<=' ') j++;

    // line of declaration
    int line=search_line(i);

    // reformat prototype
    char *proto=(char*)malloc(end-i+1/*;*/+16/*number*/+1/*\0*/);
    int n_proto=0;

    // add line, ';'
    n_proto+=sprintf(&proto[n_proto], "%x;", line);

    // add name
    memcpy(&proto[n_proto], &data[i], n_method_name);
    n_proto+=n_method_name;
    proto[n_proto++]='(';

    // parameters
    int space=0;
    char last_char=0;
    while(j<end)
    {
	// pass spaces
	if((unsigned char)data[j]<=' ')
	{
	    space++;
	    j++;
	    continue;
	}

	// default arguments
	if(data[j]=='=')
	{
	    while(data[j]!=',' && data[j]!=')') j++;
	    continue;
	}

	// space control
	if(space &&
	   last_char!='*' && last_char!=',' && last_char!='[' && last_char!=']' && last_char!='.' && last_char!='&' && last_char!=')' &&
	   data[j]  !='*' && data[j]  !=',' && data[j]  !='[' && data[j]  !=']' && data[j]  !='.' && data[j]  !='&' && data[j]  !=')')
	    proto[n_proto++]=' ';
	space=0;

	proto[n_proto++]=data[j];
	last_char=data[j];
	j++;
    }

    // add '\0'
    proto[n_proto]=0;

    // add to string_index
    if(!doc) doc=new string_index;
    doc->search_add_string(proto);
    free(proto);
}

void source_file::dump_featured_lines()
{
    int j=0;
    for(int i=0; i<n_feat; i++)
    {
	if(!j) printf("\t");
	printf("%5d ", feat[i]);
	j=(j+1)&0xf;
	if(!j) printf("\n");
    }
    if(j) printf("\n");
}

void source_file::dump_comments()
{
    for(int i=0; i<n_feat; i++)
    {
	char *str=comment(feat[i]+1);
	if(str) printf("\t%5d: @@%s@@\n", feat[i]+1, str);
    }
}

void source_file::dump(string_index *attr_index)
{
    printf("\tCOMMENTS & ATTRIBUTES:\n");
    for(int i=0; i<n_com; i++)
    {
	printf("\t%4d LINES:{%4d-%4d} ATTRS:{%4d %4d}\n\t\t{%s}\n", i,
	       com[i].ini_line+1, com[i].end_line+1, com[i].i_attr, com[i].i_attr_block,
	       &com_str[com[i].i_com]);
	if(attr_index)
	{
	    printf("\t\t{%s}\n\t\t{%s}\n",
		   attr_index->string(com[i].i_attr),
		   attr_index->string(com[i].i_attr_block));
	}
    }

    printf("\tFEATURED LINES\n");
    for(int i=0; i<n_feat; i++)
	printf("\t%4d: {%s}\n", feat[i]+1, comment(feat[i]+1));

    printf("\tSOURCE CODE\n");
    for(int i=0; i<n_line; i++)
	printf("\t%4d: ##%s##\n", i+1, &data[line[i]]);
}

void source_file::set_i_name(int i_name_)
{
    i_name=i_name_;
}

// CLASS SOURCE

source::source()
{
    // files
    util::ctor_vector(file, n_file, max_file);

    // doc
    doc=NULL;

    // attributes
    attr_prefix=attr_separator=attr_ini=attr_fin=attr_neg=attr_offline=NULL;
    attr_index=NULL;
}

source::~source()
{
    // files
    util::dtor_vector_delete<source_file*>(file, n_file, max_file);

    // attributes
    if(attr_prefix) free(attr_prefix);
    if(attr_separator) free(attr_separator);
    if(attr_ini) free(attr_ini);
    if(attr_fin) free(attr_fin);
    if(attr_neg) free(attr_neg);
    if(attr_offline) free(attr_offline);
    if(attr_index) delete attr_index;
}

bool source::init(string_index *doc_, char *prefix, char *separator, char *ini, char *fin, char *neg, char *offline)
{
    // delete files
    util::dtor_vector_delete<source_file*>(file, n_file, max_file);

    doc=doc_;

    // attr
    if(attr_prefix) free(attr_prefix);
    if(attr_separator) free(attr_separator);
    if(attr_ini) free(attr_ini);
    if(attr_fin) free(attr_fin);
    if(attr_neg) free(attr_neg);
    if(attr_offline) free(attr_offline);
    if(attr_index) delete attr_index;

    if(prefix && separator && ini && fin && neg)
    {
	attr_prefix=strdup(prefix);
	attr_separator=strdup(separator);
	attr_ini=strdup(ini);
	attr_fin=strdup(fin);
	attr_neg=strdup(neg);
	attr_index=new string_index;
    }
    else
    {
	attr_prefix=attr_separator=attr_ini=attr_fin=attr_neg=NULL;
	attr_index=NULL;
    }
    if(offline) attr_offline=strdup(offline); else attr_offline=NULL;
    return true;
}

bool source::reindex(string_index *doc_)
{
    for(int i=0; i<n_file; i++)
    {
	char *filename;
	if(!(filename=doc->string(file[i]->get_i_name()))) ERROR_RX(false, "internal: doc doesn't have file name");
	file[i]->set_i_name(doc_->search_add_string(filename));
    }
    doc=doc_;
    return true;
}

int source::search_file(int i_name)
{
    for(int i=0; i<n_file; i++) if(i_name==file[i]->get_i_name()) return i;
    return -1;
}

int source::search_or_add_file(int i_name)
{
    int i;
    if((i=search_file(i_name))<0)
    {
	i=util::inc_vector(file, n_file, max_file, INC_FILE);
	file[i]=new source_file;
	file[i]->init(i_name, doc, attr_prefix, attr_separator, attr_ini, attr_fin, attr_neg, attr_offline, attr_index);
    }
    return i;
}

bool source::add_feature_line(int i_name, int line)
{
    int i;
    if((i=search_or_add_file(i_name))>=0)
	return file[i]->add_feature_line(line);
    else
	return false;
}

char *source::comment(int i_name, int line)
{
    int i;
    if((i=search_or_add_file(i_name))>=0)
	return file[i]->comment(line);
    else
	return NULL;
}

char *source::attributes(int i_name, int line, char *namespc, char *name)
{
    if(!attr_index) return NULL; // cannot do anything w/o the parameters

    int i;
    if((i=search_or_add_file(i_name))>=0)
    {
	char *attr=file[i]->attributes(line, attr_index, attr_separator, attr_neg);
	if(namespc || name)
	{
	    int size=5; // @x2 + :: + \0
	    if(namespc) size+=strlen(namespc);
	    if(name) size+=strlen(name);
	    char *locator=(char*)malloc(size);
	    if(name && namespc) size=snprintf(locator, size, "@%s::%s@", namespc, name);
	    else if(name) size=snprintf(locator, size, "@%s@", name);
	    else size=snprintf(locator, size, "@%s@", namespc);

	    // search the attributes
	    int i_attr;
	    if((i_attr=attr_index->search_prefix_string(locator))>=0)
	    {
		char *offline_attr=attr_index->string(i_attr)+size;
		if(!attr) attr=offline_attr;
		else
		{
		    // joint attrs
		    char *joint=source_file::postparse_join_attributes(attr, offline_attr, attr_separator, attr_neg);
		    int i_joint=attr_index->search_add_string(joint);
		    free(joint);
		    attr=attr_index->string(i_joint);
		}
	    }
	    free(locator);
	}
	return attr;
    }
    else
	return NULL;
}

int source::match_regex(int i_name, char *regex, int line_min, int line_max)
{
    int i;
    if((i=search_or_add_file(i_name))>=0)
	return file[i]->match_regex(regex, line_min, line_max);
    else
	return -1;
}

string_index *source::locate_class_method(int i_name_class, int i_filename, int line, int i_method_name)
{
    int i;
    if((i=search_or_add_file(i_filename))>=0)
	return file[i]->locate_class_method(i_name_class, line, i_method_name, doc);
    else
	return NULL;
}

bool source::has_attr(char *attr_list, char *attr)
{
    if(!attr_list || !attr) return false;
    char *found=attr_list;
    int n_attr=strlen(attr);
    while((found=string::search(found, attr, const_cast<char*>("()\"\""))))
    {
	if((found==attr_list || (!isalnum(found[-1]) && found[-1]!='_' && found[-1]!='~')) &&
	   (!found[n_attr] || (!isalnum(found[n_attr]) && found[n_attr]!='_')))
	    return true;
	found+=n_attr;
    }
    return false;
}

bool source::has_neg_attr(char *attr_list, char *attr)
{
    if(!attr_list || !attr) return false;
    char *found=attr_list;
    int n_attr=strlen(attr);
    while((found=string::search(found, attr, const_cast<char*>("()\"\""))))
    {
	if(found!=attr_list && found[-1]=='~' &&
	   (!found[n_attr] || (!isalnum(found[n_attr]) && found[n_attr]!='_')))
	    return true;
	found+=n_attr;
    }
    return false;
}

void source::dump_featured_lines(int i_filename)
{
    for(int i=0; i<n_file; i++)
    {
	if(i_filename<0 || i_filename==file[i]->get_i_name())
	{
	    printf("File: %s\n", doc->string(file[i]->get_i_name()));
	    file[i]->dump_featured_lines();
	}
    }
}

void source::dump_comments(int i_filename)
{
    for(int i=0; i<n_file; i++)
    {
	if(i_filename<0 || i_filename==file[i]->get_i_name())
	{
	    char *name=doc->string(file[i]->get_i_name());
	    if(name[strlen(name)-1]=='h' && name[strlen(name)-2]=='h')
	    {
		printf("File: %s\n", doc->string(file[i]->get_i_name()));
		file[i]->dump_comments();
	    }
	}
    }
}

void source::dump(int i_filename)
{
    for(int i=0; i<n_file; i++)
    {
	if(i_filename<0 || i_filename==file[i]->get_i_name())
	{
	    char *name=doc->string(file[i]->get_i_name());
	    if(name[strlen(name)-1]=='h' && name[strlen(name)-2]=='h')
	    {
		printf("File: %s\n", doc->string(file[i]->get_i_name()));
		file[i]->dump(attr_index);
	    }
	}
    }
}

void source::dump_offline_attrs(const char *filename)
{
    FILE *arch;
    if(!filename || !(arch=fopen(filename, "w"))) arch=stdout;

    // first pass, calculate maximum namespace
    int max_namespace=5;
    for(int i=0; i<attr_index->n_string(); i++)
    {
	char *str=attr_index->i_string(i), *split;
	int size;
	if(*str=='@' && (split=strchr(str+1, '@')) && (size=(int)(split-str))>max_namespace)
	    max_namespace=size;
    }

    // format
    char format[MAX_FORMAT];
    snprintf(format, MAX_FORMAT, "%%-%ds: %%s\n", max_namespace);
    fprintf(arch, format, "Namespace", "Attributes\n");

    for(int i=0; i<attr_index->n_string(); i++)
    {
	char *str=attr_index->i_string(i), *split;
	if(*str=='@' && (split=strchr(str+1, '@')))
	{
	    *split=0;
	    fprintf(arch, format, str+1, split+1);
	    *split='@';
	}
    }

    // close
    if(arch!=stdout) fclose(arch);
}
